package platform.resource;

import com.alibaba.fastjson.JSONObject;
import framework.sepal4java.common.socket.CmdMessageGrpIds;
import framework.sepal4java.common.struct.*;
import framework.sepal4java.common.struct.sync.SynchronousString;
import platform.appmgr.AppMgr;
import platform.appmgr.AppMgrThread;
import platform.pubsub.Channel;
import platform.pubsub.Publisher;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class SensorMgr {
    private ValueType sensorType;
    private String sensorName;
    private List<String> fieldNames;
    private boolean isAlive = true;
    private long MIN_VALUE_FREQ;
    private long MAX_VALUE_FREQ;
    private ValueThread valueThread = null;
    private final Set<AppMgr> apps = ConcurrentHashMap.newKeySet();
    private final TimeLine timeLine = new TimeLine();

    public SensorMgr(JSONObject object){
        sensorName = object.getString("name");
        try {
            sensorType = ValueType.fromString(object.getString("valueType"));
        } catch (Exception e) {
            sensorType = ValueType.STRING;
        }
        try {
            fieldNames = object.getJSONArray("fields").toJavaList(String.class);
        } catch (NullPointerException e) {
            fieldNames = new ArrayList<>();
            fieldNames.add("default");
        }
        try {
            MIN_VALUE_FREQ = object.getLong("minValueFreq");
        } catch (NullPointerException e) {
            MIN_VALUE_FREQ = 1;
        }
        try {
            MAX_VALUE_FREQ = object.getLong("maxValueFreq");
        } catch (NullPointerException e) {
            MAX_VALUE_FREQ = 1000;
        }
    }

    public TimeLine getTimeLine() {
        return timeLine;
    }

    public boolean checkValueFreq(long freq) {
        return freq >= MIN_VALUE_FREQ && freq <= MAX_VALUE_FREQ;
    }

    public ValueType getSensorType() {
        return sensorType;
    }

    public String getSensorName() {
        return sensorName;
    }

    public List<String> getFieldNames() {
        return fieldNames;
    }

    public boolean isAlive() {
        return isAlive;
    }

    public void setAlive(boolean isAlive) {
        this.isAlive = isAlive;
    }

    public boolean isGetValueRunning() {
        return valueThread != null;
    }

    public void startGetValue() {
        if (valueThread != null) {
            stopGetValue();
        }
        valueThread = new ValueThread();
        valueThread.start();
    }

    public void stopGetValue() {
        if (valueThread != null) {
            valueThread.stopThread();
        }
        valueThread = null;
    }

    public Set<AppMgr> getApps() {
        return apps;
    }

    public List<String> getAppsName() {
        List<String> appNames = new ArrayList<>();
        for (AppMgr app : apps) {
            if (!app.getAppName().equals("WebServer")) {
                appNames.add(app.getAppName());
            }
        }
        return appNames;
    }

    @Override
    public String toString() {
        return new SensorInfo(sensorName, sensorType, fieldNames, isAlive ? State.ON : State.OFF, getAppsName()).toString();
    }

    public SensorInfo getSensorInfo() {
        return new SensorInfo(sensorName, sensorType, fieldNames, isAlive ? State.ON : State.OFF, getAppsName());
    }

    public class ValueThread extends Thread {
        private volatile boolean shouldStop = false;
        private volatile boolean stopped = true;

        @Override
        public void run() {
            stopped = false;
            Map<String, AppMgr> appMgrMap = AppMgrThread.getAppMgrs();
            while (!shouldStop) {
                List<TimeNode> nodes;
                synchronized (timeLine) {
                    if (timeLine.size() == 0) {
                        try {
                            timeLine.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    nodes = timeLine.getNodes();
                }
                long timestamp = 0;
                for (TimeNode node : nodes) {
                    try {
                        Thread.sleep(node.time - timestamp);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                    List<Integer> grpIds = new ArrayList<>();
                    for (String appName : node.appNames) {
                       grpIds.add(AppMgrThread.getGrpId(appName));
                    }
                    CmdMessageGrpIds send = new CmdMessageGrpIds("sensory_request",
                            null, grpIds);
                    for (String appName : node.appNames) {
                        Map<String, SynchronousString> requestMap = appMgrMap.get(appName).getRequestMap();
                        if (!requestMap.containsKey(Channel.getSensor(sensorName))) {
                            requestMap.put(Channel.getSensor(sensorName), new SynchronousString());
                        }
                        requestMap.get(Channel.getSensor(sensorName)).put("passiveGetSensorData");
                    }
                    Publisher.publish(Channel.getSensorRequest(sensorName), send.toString());

                    timestamp = node.time;
                }
            }
            stopped = true;
        }

        public void stopThread() {
            shouldStop = true;
            synchronized (timeLine) {
                timeLine.notify();
            }
            while (!stopped) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
